﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using RayDen.Library.Core;
using RayDen.Library.Core.Primitives;
using RayDen.Library.Data.Imaging;
using RayDen.RayEngine.Core.Types;
using RayDen.SmallRT.Components;

namespace RayDen.SmallRT.Integrators
{
    internal class SingleRandomWavelengthPathTracerIntegrator : BaseIntegrator
    {
        public const int MaxDepth = 16;
        public const int MaxSpectralSamples = 16;



        protected string stats;
        protected long totalSamples = 0, pass = 0;
        internal override void RunRenderer(CancellationToken cancellationToken)
        {
            Parallel.For(0, Threads, new ParallelOptions()
            {
                CancellationToken = cancellationToken,
                MaxDegreeOfParallelism = Threads
            }, (i) =>
            {
                this.InitPass();
                this.RenderPass();
            });
        }

        public override void InitPass()
        {


            //Scene.Reflectances[4] = ColorFactory.CreateRegular(SPD_Data.FromFile(@"F:\3D\spds\metals\Cu.eta.spd").Select(i => (float)i).ToArray());

            //Scene.Reflectances[5] = ColorFactory.CreateRegular(SPD_Data.FromFile(@"F:\3D\spds\Gems\Reflectance\ruby.a").Select(i => (float)i).ToArray());

            //Scene.Reflectances[2] = ColorFactory.CreateRegular(SPD_Data.FromFile(@"F:\3D\spds\Gems\Reflectance\amethyst.a").Select(i => (float)i).ToArray());

            //Scene.Reflectances[3] = ColorFactory.CreateRegular(SPD_Data.FromFile(@"F:\3D\spds\Gems\Reflectance\Emerald3.a.txt").Select(i => (float)i).ToArray());

            //Scene.Reflectances[8] = SPD_Data.RegularSpdFromSampled(SPD_Data.Al_k, SpectrumType.Illuminant);
            //ColorFactory.CreateRegular(SPD_Data.FromFile(@"F:\3D\spds\Lights\Butane.spd").Select(i => (float)i).ToArray());
        }

        public float SampleWavelength(float u0)
        {

            return SampledSpectrum.sampledLambdaStart + u0 * (SampledSpectrum.sampledLambdaEnd - SampledSpectrum.sampledLambdaStart);
        }

        public override void RenderPass()
        {
            var sb = film.GetFreeSampleBuffer();
            var sw = Stopwatch.StartNew();
            var samplesPerPass = Scene.w * Scene.h;
            for (int y = 0; y < Scene.h; ++y)
            {
                int pj = 0;
                for (int x = 0; x < Scene.w; ++x, ++pj)
                {
                    var pixV = SamplePath(x, y);
                    sb.SplatSample(x, (Scene.h - y - 1), ref pixV);
                    if (sb.IsFull())
                    {
                        film.SplatSampleBuffer(true, sb);
                        sb = film.GetFreeSampleBuffer();
                    }
                }

            }
            pass++;
            sw.Stop();
            stats = string.Format("MSamples per sec {1:F5}; Total Samples {0}; Samples per pixel {2}; {3}", totalSamples, 0.000001 * (samplesPerPass / sw.Elapsed.TotalSeconds), pass, film.GetStats());
        }

        protected virtual RgbSpectrum SamplePath(int x, int y)
        {
            float lambda;
            Tuple<float, float>[] c = new Tuple<float, float>[MaxSpectralSamples];
            float[] v = new float[MaxSpectralSamples];
            float dLambda = (SampledSpectrum.sampledLambdaEnd - SampledSpectrum.sampledLambdaStart) / ((float)MaxSpectralSamples + 0f);

            for (int l = 0; l < MaxSpectralSamples; l++)
            {
                lambda = SampledSpectrum.sampledLambdaStart + l * dLambda;
                RayData cameraRay;
                Scene.GenerateCameraRay(x, y, out cameraRay);
                var pix = EvalRadiance(ref cameraRay, lambda, 0);
                totalSamples++;
                v[l] = pix;
                c[l] = new Tuple<float, float>(lambda, pix);
                //lambda += dLambda;
            }
            //c = c.OrderBy(item => item.Item1).ToArray();
            //var lms = c.Select(p => p.Item1).ToArray();
            //var vals = c.Select(p => p.Item2).ToArray();
            //var spd = new IrregularSPD(lms, vals, MaxSpectralSamples, dLambda);
            RegularSPD spd = new RegularSPD(v, SampledSpectrum.sampledLambdaStart, SampledSpectrum.sampledLambdaEnd, MaxSpectralSamples);
            //RgbSpectrum pixV = new RgbSpectrum();
            //ColorFactory.SSEToRgb(spd, ref pixV);
            var pixV = spd.ToRgb();
            return pixV;
        }


        protected virtual RgbSpectrum SamplePath1(int x, int y)
        {
            float lambda;
            Tuple<float, float>[] c = new Tuple<float, float>[MaxSpectralSamples];
            float dLambda = (SampledSpectrum.sampledLambdaEnd - SampledSpectrum.sampledLambdaStart) / ((float)MaxSpectralSamples + 1f);

            for (int l = 0; l < MaxSpectralSamples; l++)
            {
                lambda = SampleWavelength(rnd.NextFloat());
                RayData cameraRay;
                Scene.GenerateCameraRay(x, y, out cameraRay);
                var pix = EvalRadiance(ref cameraRay, lambda, 0);
                totalSamples++;
                c[l] = new Tuple<float, float>(lambda, pix);
                //lambda += dLambda;
            }
            c = c.OrderBy(item => item.Item1).ToArray();
            var lms = c.Select(p => p.Item1).ToArray();
            var vals = c.Select(p => p.Item2).ToArray();
            var spd = new IrregularSPD(lms, vals, MaxSpectralSamples, dLambda);
            //RegularSPD spd = new RegularSPD(vals, SampledSpectrum.sampledLambdaStart, SampledSpectrum.sampledLambdaEnd, MaxSpectralSamples);
            var pixV = spd.ToRgb();
            return pixV;
        }

        public override string GetStats()
        {
            return stats;
        }

        private float EvalRadiance(ref RayData r, float lambda, int depth)
        {
            double t = 0; // distance to intersection 
            int id = 0; // id of intersected object 
            if (!Scene.Intersect(ref r, ref t, ref id) || depth > MaxDepth)
            {
                return 0f;
            }
            var obj = SceneManager.Sph[id];
            var x = r.Point((float)t);
            Normal n = (Normal)(x - obj.p).Normalize();
            var nl = Vector.Dot(ref n, ref r.Dir) < 0 ? n : n * -1;
            var rf = Scene.Reflectances[id];
            var f = rf.Sample(lambda);
            var e = 0f;
            var p = f;
            if (obj.refl == 3)
            {
                e += f;
            }
            if (++depth > MaxDepth)
            {
                if (rnd.NextFloat() < p)
                {
                    f = f * (1f / p);
                }
                else
                {
                    return e;
                }
            }

            var brdf = Brdfs[obj.refl];
            Vector dir;
            float fb;
            brdf.Sample(ref r.Dir, ref n, rnd.NextFloat(), rnd.NextFloat(), lambda, out dir, out fb);
            RayData newRay = new RayData(ref x, ref dir);
            return e + f * fb * EvalRadiance(ref newRay, lambda, depth);
        }
    }
}
